App.require(function () {
    var app = App.getCurrent();

    app.defineComponent("str-form", {
        render: function (next) {
            this.computeData();
            this.validates = {};
            this.validateObj = {};
            this.fetch("./index.html", function (text) {
                next(this.renderHTML(text));
            })
        },
        getDomObj: function () {
            this.attachDom("form", "form")
                .attachEvent("form", "reset", this.resetHandler, false)
                .attachEvent("form", "invalid", this.invalidHandler, true) // 如果是false，就不会触发表单的invalid事件
                .attachEvent("form", "submit", this.submitHandler, false);
        },
        _getErrorKey: function (key) {
            return {
                patternMismatch: "pattern",
                rangeOverflow: "max",
                rangeUnderflow: "min",
                tooLong: "maxLength",
                tooShort: "minLength",
                typeMismatch: "type",
                valueMissing: "required",
                customError: "custom"
            }[key];
        },
        _getDefaultMessage: function (target, key) {
            var str = {
                patternMismatch: "pattern",
                rangeOverflow: "max",
                rangeUnderflow: "min",
                tooLong: "maxLength",
                tooShort: "minLength",
                typeMismatch: "type",
                valueMissing: "required",
                customError: "custom"
            }[key];
            return target.dataset[str] || {
                patternMismatch: "格式不对",
                rangeOverflow: "超过最大范围",
                rangeUnderflow: "低于最小范围",
                tooLong: "长度大于最大值",
                tooShort: "长度小于最小值",
                typeMismatch: "类型不匹配",
                valueMissing: "该字段为必填",
                customError: target.validationMessage
            }[key];
        },
        invalidHandler: function (ev) {
            ev.preventDefault();
            this.domList.form.classList.add("checkform");
            var target = ev.target, name = target.name, validateObj = this.validateObj;
            var validity = target.validity, that = this, component;
            var validateKeys = ["valueMissing", "patternMismatch", "rangeOverflow", "rangeUnderflow", "tooLong", "tooShort", "typeMismatch"];
            for (var i = 0; i < validateKeys.length; i++) {
                var errorKey = validateKeys[i];
                if (validity[errorKey]) {
                    var key = this._getErrorKey(errorKey), d = validateObj[name] ? validateObj[name][key] : null;
                    this.data.checkInvalid = true;
                    var text = d ? d.data.text : this._getDefaultMessage(target, errorKey);
                    this.dispatchCustomEvent("forminvalid", {
                        component: this,
                        text: text,
                        target: target
                    });
                    component = this._getComponentByDom(target);
                    that._setReInput(target, text, component);
                    return;
                } 
            }
        },
        removeCheck: function () {
            this.domList.form.classList.remove("checkform");
            var components = this.getComponentsByName(["str-text", "str-textarea"]);
            for (var i = 0; i < components.length; i++) {
                components[i].assistive = {
                    text: "", type: ""
                };
            }
        },
        submitHandler: function (ev) {
            if (ev) ev.preventDefault();
            var obj = this._getSubmitDataAndAsyncFn(), that = this;
            this.dispatchCustomEvent("formcheckasync", {
                component: this
            })
            this._renderAsync(obj.asyncList, obj.data, function (result) {
                if (result.OK) {
                    var event = that.getCanCancelEvent();
                    that.dispatchCustomEvent("formbeforesubmit", {
                        event: event,
                        component: that,
                        data: obj.data,
                        form: that.domList.form
                    });
                    if (!event.isCancel) {
                        that.ajaxSubmit(obj.data, function (res, data) {
                            that.dispatchCustomEvent("formaftersubmit", {
                                component: that, 
                                data: data,
                                originData: res
                            })
                        })
                    }
                }
                else {
                    that.domList.form.classList.add("checkform");
                    var target = that.domList.form[result.name], text = result.text;
                    var component = this._getComponentByDom(target);
                    that._setReInput(target, text, component);
                    target.setCustomValidity(text);
                    that.dispatchCustomEvent("forminvalid", {
                        component: that,
                        text: text,
                        target: target
                    })
                }
            })
        },
        _setReInput: function (target, text, component) {
            var inputHandler = function (ev) {
                ev.target.setCustomValidity("");
                ev.target.removeEventListener("input", inputHandler, false);
                if (component) component.assistive = { type: "", text: "" };
            }
            target.addEventListener("input", inputHandler, false);
            if (component) component.assistive = { type: "error", text: text };
        },
        ajaxSubmit: function (data, feeback) {
            var page = this._getPage();
            var method = this.property.method, action = this.property.action,
                enctype = this.property.enctype;

            if (method.toUpperCase() == "POST") {
                page.post(action, data, feeback, enctype ? {
                    header: { "Content-Type": enctype }
                }: undefined);
            }
            else if (method.toUpperCase() == "GET") {
                page.get(action + "?" + App.serialize(data), feeback, enctype ? {
                    header: { "Content-Type": enctype }
                }: undefined);
            }
        },
        _renderAsync: function (fnList, data, bk) {
            if (fnList.length === 0) return bk({ OK: true });
            var fbObj = fnList.shift(), value = data[fbObj.name], that = this;
            fbObj.feeback(value, data, function (obj) {
                if (obj.OK) that._renderAsync(fnList, data, bk);
                else bk({ OK: false, text: obj.text, name: fbObj.name, value: value })
            })
        },
        _getSubmitDataAndAsyncFn: function () {
            var form = this.domList.form, validateObj = this.validateObj;
            var data = {}, asyncList = [];
            for (var i = 0; i < form.elements.length; i++) {
                var element = form.elements[i];
                var name = element.name, tagName = element.tagName.toUpperCase() 
                if (name &&  (tagName === "INPUT" || tagName === "SELECT" || tagName === "TEXTAREA")) {
                    switch (element.type) {
                        case "checkbox":
                            if (!data[name]) data[name] = [];
                            if (element.checked) data[name].push(element.value);
                            break;
                        case "radio":
                            if (element.checked) data[name] = element.value;
                            break;
                        default: 
                            data[name] = element.value;
                    }
                    if (name in validateObj && "feeback" in validateObj[name]) {
                        asyncList.push({ name: name, feeback: validateObj[name].feeback });
                    }
                }
            }
            return {
                data: data,
                asyncList: asyncList
            }
        },
        resetHandler: function (ev) {
            var components = this.components;
            for (var i = 0; i < components.length; i++) {
                if (typeof components[i].reset === "function") components[i].reset();
            }
            this.dispatchCustomEvent("formreset", {
                component: this,
                event: ev
            });
        },
        submit: function () {
            this.domList.form.submit();
        },
        computeData: function () {
            var property = this.property;
            property.name = property.name || this.uid;
            property.action = property.action || location.href;
            property.method = property.method || "get";
            property.enctype = property.enctype || "";
            property.class = property.class || "";
            this.data.propertystr = this.createPropertyStr();
        },
        // 讲验证信息放在表单元素中, 作为css验证
        resetValidate: function () {
            var form = this.domList.form, validates = this.validateObj;
            for (var i = 0; i < form.elements.length; i++) {
                var element = form.elements[i], name = element.name;
                if (name && name in validates) {
                    this._initElementValidate(element, validates[name]);
                }
            }
        },
        _getValidateMap: function () {
            var validates = this.validates, obj = {};
            for (var key in validates) {
                var list = validates[key];
                var itemObj = obj[key] = {};
                for (var i = 0; i < list.length; i++) {
                    var item = list[i];
                    ["required", "pattern", "type", "maxLength", "minLength", "min", "max", "step"].forEach(function (str) {
                        if (str in item) {
                            var d = itemObj[str] = { data: {} };
                            d.data.value = item[str];
                            d.data.text = item.text;
                        }
                    });
                    if ("feeback" in item) {
                        itemObj.feeback = item.feeback;
                    }
                }
            }
            return obj;
        },
        _initElementValidate: function (element, obj) {
            ["required", "pattern", "type", "maxLength", "minLength", "min", "max", "step"].forEach(function (item) {
                if (obj[item]) {
                    var data = obj[item].data;
                    if (item === "pattern" && data.value.constructor === RegExp) element[item] = data.value.source;
                    else element[item] = data.value

                    element.dataset[item] = data.text;
                }
            })
        },
        setValidate: function (obj) {
            this.validates = obj;
            this.validateObj = this._getValidateMap();
            this.resetValidate();
        },
        get className() {
            return this.property.class;
        },
        set className(value) {
            if (this.property.class == value) return;
            this.domList.form.className = value;
            this.property.class = value;
        },
        get name() {
            return this.property.name;
        },
        set name(value) {
            if (value === this.property.name) return;
            this.domList.form.setAttribute("name", value);
            this.property.name = value;
        },
        get action() {
            return this.property.action;
        },
        set action(value) {
            if (value === this.property.action) return;
            this.domList.form.setAttribute("action", value);
            this.property.action = value;
        },
        get method() {
            return this.property.method;
        },
        set method(value) {
            if (value === this.property.method) return;
            this.domList.form.setAttribute("method", value);
            this.property.method = value;
        },
        set enctype(value) {
            if (value === this.property.enctype) return;
            this.domList.form.setAttribute("enctype", value);
            this.property.enctype = value;
        },
        get enctype() {
            return this.property.enctype;
        },
        set novalidate(value) {
            value = value ? "novalidate" : "";
            if (value === this.property.novalidate) return;
            if (value) {
                this.domList.form.setAttribute("novalidate", "");
            }
            else {
                this.domList.form.removeAttribute("novalidate");
            }
            this.property.novalidate = value;
        },
        get novalidate() {
            return this.property.novalidate;
        }
    });
})